package dse.messages;

import dse.*;

/**
 * The message class is used to simulate the transfer of data between nodes.  
 * The behavior associated with messages is contained in their MessageHandler
 * object.  This handler is invoked when the message is "received".
 * 
 * @author Sam
 */
public class Message {
	private int id;
	private MessageType type;
	private MessagePurpose purpose;
	private Node from;
	private Node to;
	private long size;
	//private double timeCostPerByte;
	private double distance;
	private MessageHandler handler;
	
	private double startTime;
	private double deliverTime;
	//private long bytesSent;
	private double timeToFinish;
	private boolean isFinished;
	
	public Message (
			int id,
			MessageType type,
			MessagePurpose purpose,
			Node from, 
			Node to, 
			long size,
			double totalTimeRequired,
			double distance,
			MessageHandler handler
			)
	{
		assert totalTimeRequired > 0;
		
		this.id = id;
		this.type = type;
		this.purpose = purpose;
		this.from = from;
		this.to = to;
		this.size = size;
		//this.timeCostPerByte = totalTimeRequired/size;
		this.distance = distance;
		this.handler = handler;
		
		//this.bytesSent = 0;
		this.timeToFinish = totalTimeRequired;//timeCostPerByte*size;
		this.isFinished = false;
		this.startTime = 0;
		this.deliverTime = startTime + timeToFinish;
	}
	
	public int id () { return id; }
	public MessageType messageType () { return type; }
	public MessagePurpose purpose () { return purpose; }
	public Node from () { return from; }
	public Node to () { return to; }
	public double distance () { return distance; }
	public long size () { return size; }
	public boolean isFinished () { return isFinished; }
	public void  isFinished(boolean f){isFinished = f;}
	public MessageHandler handler () { return handler; }
	public double startTime() { return startTime; }
	public void startTime(double time){	startTime = time; }
	public double endTime() { return startTime + timeToFinish; }
	public double deliverTime() { return deliverTime;}
	public void deliverTime(double t){deliverTime = t;}
	
	/**
	 * This method is called to process a message.  Processing here means 
	 * simulating transfer of more message bytes.  This should return the 
	 * time used and the bandwidth used as a pair.  
	 * 
	 * @param timePassed			The amount of time that should be processed.
	 * @param bandwidthAvailable	The max number of bytes that can be sent.
	 * @param recorder				
	 * @return	The amount time in the passed time period that was not used.
	 */
	public void process ( 
			//int bandwidthAvailable,
			//Experiment experiment,
			//ExperimentRecorder recorder
			)
	{
		/*recorder.messageTransferStarted(id, type, distance, size, startTime);
		if (handler.acceptMessage(this))
		{
			if (from.isDown()) {
				// Then the message cannot be delivered and we should fail 
				// it...
				recorder.messageTransferInterupted(id, type, startTime);
				handler.messageInterupted(recorder, this, startTime);
				return;
			}
			
			endTime = startTime + timeToFinish;
		}
			int bndwT = experiment.getRemainingNodeBandwidth(to);
			int bndwF = experiment.getRemainingNodeBandwidth(from);
			
			if (bndwT <= 0)
				recorder.debug(this, "Node " + to + " is out of bandwidth", startTime);
			if (bndwF <= 0)
				recorder.debug(this, "Node " + from + " is out of bandwidth", startTime);
			
			if (bndwT <= 0 || bndwF <= 0)  {
				recorder.debug(this, "No bandwidth for " + this, startTime);
				return null;
			}
			int bndw = Math.min(
					Math.min(bndwT, bndwF), 
					experiment.configuration().getBandwidthProcessingChunk()
					);
			
			recorder.messageProcessed(id, type);
			
			// It is important to remember that transfer can be limited by 
			// available bandwidth.
			
			long bytesWeWantToSend = Math.min(bandwidthAvailable, size-bytesSent);
			double actualTimeToProcess = bytesWeWantToSend*timeCostPerByte;
				
			timeToFinish = timeToFinish - actualTimeToProcess;
			
			if (timeToFinish <= 0.0000000001) {
				recorder.bytesSent(id, type, purpose, distance, size - bytesSent);
				isFinished = true;
				return new Pair<Double,Integer>(
						timeReqToSendWhatWeWant,
						(int)(size - bytesSent)
						);
			}
			
			long bytesSentThisTime = (long)((actualTimeToProcess)/timeCostPerByte);
			bytesSent += bytesSentThisTime; 
			assert bytesSent < size; // this might fail occasionally, thats OK
			recorder.bytesSent(id, type, purpose, distance, bytesSentThisTime, size-bytesSent);
			return new Pair<Double,Integer>(
					actualTimeToProcess, 
					(int)(bytesSentThisTime)
					);
		 	int bndwUsed = res.second;
			experiment.decrementRemainingNodeBandwidth(m.to(), bndwUsed);
			experiment.decrementRemainingNodeBandwidth(m.from(), bndwUsed);*/
	}

	/**
	 * Useful for debugging. 
	 * @return
	 */
	@Override
	public String toString () {
		return "";/*String.format(
				"msg#%d{%s [%db] (%s) -> (%s)}",
				new Object [] {id, type.toString(), size, 
						from.toString(), to.toString()}
				);*/
	}
}
